﻿
//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "CmdPickWithPicker.h"
#include "IPick.h"
#include "IPicker.h"
#include "ILine3d.h"
#include "Vector3d.h"
#include "Matrix4d.h"
#include "IDocument.h"
#include "ActionBase.h"
#include "ISelection.h"
#include "IModelView.h"
#include "IPickFilter.h"
#include "IPickTarget.h"
#include "IPickResult.h"
#include "IUiDocument.h"
#include "IHighlights.h"
#include "UiViewUtils.h"
#include "IPreHighlights.h"
#include "IPickEventArgs.h"
#include "IViewerContext.h"
#include "IGraphicsPoint.h"
#include "IPickCandidates.h"
#include "IGraphicsCurve3d.h"
#include "GcmpCommandState.h"
#include "IRectPickContext.h"
#include "IPickEventHandler.h"
#include "IPointPickContext.h"
#include "TransferOwnership.h"
#include "UiDocumentViewUtils.h"
#include "IPureGraphicsElement.h"
#include "IGraphicsElementShape.h"
#include "IGraphicsNodeReference.h"
#include "PickNodeReferenceOption.h"

#include "PickFilterDlg.h"
#include "CommandRegister.h"
#include "SampleUIUtils.h"
#include "PickSnapCommandIds.h"
#include "CmdPickModuleEntry.h"
#include "ICanvas.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 

using namespace gcmp;
using namespace Sample;

REGISTER_COMMAND(CmdPickWithPicker)

namespace Sample
{
#pragma region 拾取过滤器和拾取事务
    class PickWithPickerFilter : public IPickFilter
    {

    public:
        PickWithPickerFilter(PickFilterDlg* pPickFilterDlg)
        {
            m_pickFilterDlg = pPickFilterDlg;
        }

        virtual bool AllowElement(const gcmp::ElementId& elementId) const override
        {
            return m_pickFilterDlg->AllowElement();
        }

        virtual bool AllowGraphicsNode(const gcmp::IGraphicsNodeReference& graphicsNodeReference) const override
        {
            return m_pickFilterDlg->AllowGraphicsNode();
        }

        virtual bool SetPickTargetOption(gcmp::IPickTarget* pPickTarget) override
        {
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pPickTarget, L"pPickTarget为空",L"GDMP",L"2023-12-30");
            m_pickFilterDlg->SetPickTarget(pPickTarget);
            return true;
        }
    private:
        PickFilterDlg* m_pickFilterDlg;
    };
#pragma endregion 拾取过滤器和拾取事务

#pragma region 使用IPicker的Action
    class PickWithPickerAction : public gcmp::ActionBase
    {

    public:
        PickWithPickerAction() :
            m_isUserCancelled(false)
        {
        }

        virtual void InitAction(IUiView* pCurrentView) override
        {
            DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView, L"pCurrentView为空",L"GDMP",L"2023-12-30");
            pCurrentView->GetCanvas()->Refresh();
        }

        // 鼠标移动中会持续调用IPicker刷新拾取候选集并预高亮
        virtual bool OnMovePoint(IUiView* pCurrentView, const Vector3d& pos) override
        {
            // 因为在选择偏移拾取直线后，会绘制射线的临时图形。需要清理上一次绘制的图形
            CleanupTempGraphicsShape();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pCurrentView, L"pCurrentView nullptr",L"GDMP",L"2023-12-30");
            IModelView* pModelView = pCurrentView->GetModelView();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pModelView, L"pModelView为空",L"GDMP",L"2023-12-30");

            IDocument* pDocument = pModelView->GetDocument();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pDocument, L"无效的文档指针",L"GDMP",L"2023-12-30");

            // 将模型坐标系的鼠标位置转换为有向直线
            OwnerPtr<ILine3d> opRay = UiViewUtility::WorldPositionToWorldRay(pCurrentView, pos);
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opRay, L"opRay nullptr",L"GDMP",L"2023-12-30");
            // 如果选择偏移拾取直线，则创建一条和屏幕不垂直有角度的有向直线，并且绘制临时图形
            if (CmdPickModuleEntry::DoOffsetRay())
            {
                opRay = OffsetAndDrawRay(pCurrentView, pos);
            }

            // 获得当前视图的可见图形
            std::vector<const IGraphicsElementShape*> greps = pCurrentView->GetVisibleGreps();

            PickFilterDlg* pPickFilterDlg = PickFilterDlg::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pPickFilterDlg, L"pPickFilterDlg为空",L"GDMP",L"2023-12-30");

            // 通过拾取过滤栏Ribbon菜单选项设置IPointPickContext和IPickFilter
            OwnerPtr<PickWithPickerFilter> opPickFilter = NEW_AS_OWNER_PTR(PickWithPickerFilter, pPickFilterDlg);
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opPickFilter, L"opPickFilter nullptr",L"GDMP",L"2023-12-30");
  
            // 特别注意：这里需要将像素容差转换为世界坐标系内的真实坐标；不同于IPickNodeReferenceOption::SetPickPixelTolerance中设置的是像素容差
            double pixelTolerance = CmdPickModuleEntry::GetTolerance();
#pragma warning(push)
#pragma warning(disable: 4244)
            int realTolerance = (int)UiViewUtility::ComputeWorldWidthFromPixel(pCurrentView, pixelTolerance);
#pragma warning(pop)
            bool canPickViewportCuttedGraphics = CmdPickModuleEntry::DoSelectCutViewPort();
            IPicker::SetCanPickViewportCuttedGraphics(canPickViewportCuttedGraphics);

            OwnerPtr<IPickTarget> opPickTarget = IPickTarget::Create();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opPickTarget, L"opPickTarget nullptr",L"GDMP",L"2023-12-30");
            pPickFilterDlg->SetPickTarget(opPickTarget.get());

            OwnerPtr<IViewerContext> opViewerContext = pCurrentView->GetViewerContext();
            OwnerPtr<IPointPickContext> opPickContext = IPointPickContext::Create();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opPickContext, L"opPickContext nullptr",L"GDMP",L"2023-12-30");
            opPickContext->SetDocument(pDocument);
            opPickContext->SetViewerContext(TransferOwnership(opViewerContext));
            opPickContext->SetGraphicsElementShapes(greps);
            opPickContext->SetRay(opRay.get());
            
            opPickContext->SetPickTolerance(realTolerance);
            opPickContext->SetPickTarget(TransferOwnership(opPickTarget));
            opPickContext->SetPickFilter(opPickFilter.get());
            pPickFilterDlg->SetPickContext(opPickContext.get());

            // 清理预高亮和拾取候选集
            IPreHighlights* pPreHighlights = IPreHighlights::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pPreHighlights, L"pPreHighlights为空",L"GDMP",L"2023-12-30");
            pPreHighlights->Clear();
            IPickCandidates* pPickCandidates = IPickCandidates::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pPickCandidates, L"pPickCandidates为空",L"GDMP",L"2023-12-30");
            pPickCandidates->Clear();

            // 拾取计算
            OwnerPtr<IPickResult> opPickResult = IPicker::PickByPoint(opPickContext.get());
            DBG_WARN_AND_RETURN_FALSE_UNLESS(opPickResult, L"无效的opPickResult",L"GDMP",L"2023-12-30");

            PreProcessPickCandidates(opPickResult);

            // 拾取结果不为空，则将拾取结果设置到拾取候选集并预高亮
            if (!opPickResult->IsEmpty())
            {
                int count = (int)opPickResult->GetAllGraphicsNodeReferences().size();
                OutputDebugStringW(StringUtil::ToWString(count).c_str());
                // 将拾取计算结果中最前面的拾取项添加到拾取候选集
                if (const IGraphicsNodeReference* pGNodeRef = opPickResult->GetFront())
                {
                    std::vector<OwnerPtr<IGraphicsNodeReference>> graphicsNodeReferences;
                    OwnerPtrContainerUtil::AddItem(graphicsNodeReferences, pGNodeRef);
                    pPreHighlights->AddGraphicsNodeReferences(graphicsNodeReferences);

                    OwnerPtr<IPick> opPick = IPick::Create();
                    DBG_WARN_AND_RETURN_FALSE_UNLESS(opPick, L"opPick为空",L"GDMP",L"2023-12-30");
                    opPick->AddGraphicsNodeReference(*pGNodeRef);
                    pPickCandidates->AddPick(TransferOwnership(opPick));
                    // 注意设置默认候选集index，这样IPickCandidates::GetCurrentPick()会返回设置IPick
                    pPickCandidates->SetDefaultCurrentPickIndex(pDocument);
                }
            }
            // 记得刷新视图，否则预高亮显示可能不会立刻刷新
            UpdateView();
            return true;
        }

        // 点击鼠标左键，做选择和高亮处理
        virtual bool OnLButtonDown(IUiView* pCurrentView, const Vector3d& pos) override
        {
            // 这里可以添加点击左键后的业务逻辑

            // 不是所有的拾取操作都需要选择。根据Ribbon菜单的选择拾取图元，决定是否将拾取候选集放入选择集
            if (!CmdPickModuleEntry::DoSelectPicked())
            {
                return true;
            }

            IUiDocument* pUiDocument = UiDocumentViewUtils::GetCurrentUiDocument();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pUiDocument, L"pUiDocument为空",L"GDMP",L"2023-12-30");
            gcmp::IDocument* pDocument = pUiDocument->GetDbDocument();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pDocument, L"pDoc为空",L"GDMP",L"2023-12-30");

            // 清理预高亮，高亮和选择集
            ISelection* pSelection = ISelection::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pSelection, L"pSelection为空",L"GDMP",L"2023-12-30");
            pSelection->Clear(pDocument);
            IHighlights* pHighlights = IHighlights::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pHighlights, L"pHighlights为空",L"GDMP",L"2023-12-30");
            pHighlights->Clear();

            IPickCandidates* pPickCandidates = IPickCandidates::Get();
            DBG_WARN_AND_RETURN_FALSE_UNLESS(pPickCandidates, L"pPickCandidates为空",L"GDMP",L"2023-12-30");
            if (const OwnerPtr<IPick>& opPick = pPickCandidates->GetCurrentPick())
            {
                DBG_WARN_AND_RETURN_FALSE_UNLESS(opPick, L"opPick为空",L"GDMP",L"2023-12-30");
                if (const IGraphicsNodeReference* pGraphicsNodeReference = opPick->GetFront())
                {
                    DBG_WARN_AND_RETURN_FALSE_UNLESS(pGraphicsNodeReference, L"pGraphicsNodeReference为空",L"GDMP",L"2023-12-30");
                    pSelection->AddGraphicsNodeReference(pDocument, *pGraphicsNodeReference);
                    pHighlights->AddGraphicsNodeReference(*pGraphicsNodeReference);
                }
            }
            // 刷新视图
            UpdateView();
            return true;
        }

        // 通过ESC键退出Action
        virtual bool OnKeyUp(IUiView* pCurrentView, int nChar)
        {
            if (VK_ESCAPE == nChar)
            {
                MarkFinishStatus(ActionFinishStatus::Cancelled);
            }
            return ActionBase::OnKeyDown(pCurrentView, nChar);
        }

        // Action退出前清理绘制的临时图像
        virtual void ActionCancelled() override
        {
            CleanupTempGraphicsShape();
        }

    private:
        void PreProcessPickCandidates(OwnerPtr<IPickResult>& opPickResult)
        {
            std::wstring tooltip = L"IPicker Result: ";
            for (const OwnerPtr<IGraphicsNodeReference>& itor : opPickResult->GetAllGraphicsNodeReferences())
            {
                DBG_WARN_AND_RETURN_VOID_UNLESS(itor, L"itor为空",L"GDMP",L"2023-12-30");
                tooltip += itor->GetDescriptionString() + L"; ";
                // TODO:GBMP-45577 信息太长会导致主窗口变形
                break;
            }
            SampleUIUtils::SetStatusBarPromptMessage(tooltip);
        }

        // 从当前鼠标位置倾斜于屏幕垂线创建拾取的有向直线，并绘制临时直线图形
        OwnerPtr<ILine3d> OffsetAndDrawRay(IUiView* pCurrentView, const Vector3d& pos)
        {
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pCurrentView, L"pCurrentView为空",L"GDMP",L"2023-12-30");

            // 绘制的直线图形需要偏离相机投影面，否则相机投影幕和鼠标位置会自动调整过度
            OwnerPtr<IViewerContext> opViewerContext = pCurrentView->GetViewerContext();
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opViewerContext, L"opViewerContext为空",L"GDMP",L"2023-12-30");
            Vector3d startPt = pos + opViewerContext->GetCameraDirection() * 1000;

            // 创建垂直于屏幕的有向直线
            OwnerPtr<ILine3d> opRay = UiViewUtility::WorldPositionToWorldRay(pCurrentView, startPt);
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opRay, L"opRay为空",L"GDMP",L"2023-12-30");

            // 围绕相机向上和向右方向的45°夹角旋转拾取直线10°
            Vector3d upDir = opViewerContext->GetCameraUpDirection();
            Vector3d rightDir = upDir.Cross(opViewerContext->GetCameraDirection());
            Vector3d axis = upDir + rightDir;
            Matrix4d rotateMatrix;
            rotateMatrix.MakeRotate(startPt, axis, Constants::MATH_PI / 36);
            opRay->Transform(rotateMatrix);

            // 绘制临时直线图形
            OwnerPtr<IGraphicsElementShape> opGraphicsElementShape = IGraphicsElementShape::Create(GraphicsRenderLayer::Model);
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opGraphicsElementShape, L"opGraphicsElementShape为空",L"GDMP",L"2023-12-30");
            opRay->SetLimits(Intervald(1000, 10000));
            OwnerPtr<IGraphicsCurve3d> opGraphicsCurve3d = IGraphicsCurve3d::Create(opRay.get());
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opGraphicsCurve3d, L"opGraphicsCurve3d为空",L"GDMP",L"2023-12-30");
            opGraphicsElementShape->AddChild(TransferOwnership(opGraphicsCurve3d));

            // 在鼠标起始位置绘制点
            OwnerPtr<IGraphicsPoint> opGraphicsPoint = IGraphicsPoint::Create(startPt);
            DBG_WARN_AND_RETURN_NULLPTR_UNLESS(opGraphicsPoint, L"opGraphicsPoint为空",L"GDMP",L"2023-12-30");
            opGraphicsPoint->SetDisplayShape(PointDisplayShapeType::Cross);
            opGraphicsElementShape->AddChild(TransferOwnership(opGraphicsPoint));
            DrawTempGraphicsElementShape(TransferOwnership(opGraphicsElementShape));

            return TransferOwnership(opRay);
        }

        void EnterPickRectangleState()
        {
            // TODO: 会继续完善
        }

        void EnterPickPolygonState()
        {
            // TODO: 会继续完善
        }

    private:
        // 缓存IPicker的输出参数
        bool m_isUserCancelled;
        gcmp::Vector3d m_ptMousePoint;
    };
}

#pragma region CmdPickWithPicker
CmdPickWithPicker::CmdPickWithPicker() : CommandBase(ID_CMD_PICK_WITH_IPICKER)
{
}

CmdPickWithPicker::~CmdPickWithPicker(){}

bool CmdPickWithPicker::IsEnabled() const
{
    return GcmpCommandState::IsInDocumentEnvironment() && (CmdPickModuleEntry::GetPickMethod() == PickMethod::IPicker);
}

bool CmdPickWithPicker::IsVisible() const
{
    return GcmpCommandState::IsInDocumentEnvironment();
}

gcmp::OwnerPtr<gcmp::IAction> CmdPickWithPicker::ExecuteCommand(const gcmp::CommandParameters& cmdParams)
{
    if (PickFilterDlg* dlg = PickFilterDlg::Get())
    {
        dlg->show();
    }
    return NEW_AS_OWNER_PTR(PickWithPickerAction);
}

// 是否在启动Command前清空之前的选择集
bool CmdPickWithPicker::ShouldClearSelectionBeforeExecution() const
{
    bool clearSelectionBeforeExecution = CmdPickModuleEntry::DoClearSelectionBeforeCmd();
    return clearSelectionBeforeExecution;
}
#pragma endregion CmdPickWithPicker

